package cn.net.xyan.easy.spring.jpa.support;

import cn.net.xyan.easy.spring.jpa.utils.query.Query;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.domain.Specifications;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.io.Serializable;
import java.util.*;


/**
 * Created by zarra on 16/1/22.
 */
@NoRepositoryBean
public class EasyJpaRepositoryImpl<ENTITY, ID extends Serializable>
        extends SimpleJpaRepository<ENTITY, ID> implements IModelQuery<ENTITY> {

    private EntityManager em;

    public EasyJpaRepositoryImpl(JpaEntityInformation<ENTITY, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.em = entityManager;
    }

    public EasyJpaRepositoryImpl(Class<?> domainClass, EntityManager em) {
        super((Class<ENTITY>) domainClass, em);
        this.em = em;
    }


    protected static Long executeCountQuery2(TypedQuery<Long> query) {

        Assert.notNull(query);

        List<Long> totals = query.getResultList();
        Long total = 0L;

        for (Long element : totals) {
            total += element == null ? 0 : element;
        }

        return total;
    }

    protected Page<Tuple> queryForModelAndResultType(Specifications<ENTITY> specification, Class<?> responseType, Pageable pageable, List<String> objProperties) {

        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Tuple> query = builder.createTupleQuery();
        Root<ENTITY> root = query.from(getDomainClass());

        Map<String, Join<?, ?>> joinContext = new HashMap<>();

        Predicate predicate = specification.toPredicate(root, query, builder);

        List<String> properties = Query.selectForClass(root, query, builder, responseType, joinContext);

        objProperties.addAll(properties);

        query.where(predicate);

        TypedQuery<Tuple> tupleTypedQuery = em.createQuery(query);

        if (pageable == null)
            return new PageImpl<>(tupleTypedQuery.getResultList());

        tupleTypedQuery.setFirstResult(pageable.getOffset());
        tupleTypedQuery.setMaxResults(pageable.getPageSize());

        Long total = executeCountQuery2(getCountQuery(specification));

        List<Tuple> content = total > pageable.getOffset() ? tupleTypedQuery.getResultList() : Collections.<Tuple>emptyList();

        return new PageImpl<>(content, pageable, total);

    }


    protected CriteriaQuery<Tuple> queryForModelAndResultType(Object model, Class<?> responseType, List<String> objProperties) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Tuple> query = builder.createTupleQuery();
        Root<ENTITY> root = query.from(getDomainClass());

        Map<String, Join<?, ?>> joinContext = new HashMap<>();

        Predicate predicate = Query.queryForModel(root, query, builder, model, joinContext);

        List<String> properties = Query.selectForClass(root, query, builder, responseType, joinContext);

        objProperties.addAll(properties);

        query.where(predicate);

        return query;
    }


    protected Page<Tuple> queryForModelAndResultType(Object model, Class<?> responseType, Pageable pageable, List<String> objProperties) {

        CriteriaQuery<Tuple> query = queryForModelAndResultType(model, responseType, objProperties);

        TypedQuery<Tuple> tupleTypedQuery = em.createQuery(query);

        if (pageable == null)
            return new PageImpl<>(tupleTypedQuery.getResultList());

        tupleTypedQuery.setFirstResult(pageable.getOffset());
        tupleTypedQuery.setMaxResults(pageable.getPageSize());

        Long total = executeCountQuery2(getCountQuery(Query.<ENTITY>querySpecificationForModel(model)));

        List<Tuple> content = total > pageable.getOffset() ? tupleTypedQuery.getResultList() : Collections.<Tuple>emptyList();

        return new PageImpl<>(content, pageable, total);

    }

    protected <T> Page<T> covertTuplePage2ObjPage(Page<Tuple> page, List<String> objProperties, Class<T> resultType, Pageable pageable) {
        List<T> content = new LinkedList<>();
        for (Tuple tuple : page.getContent()) {
            content.add(Query.tuple2Object(tuple, objProperties, resultType));
        }

        Long total = page.getTotalElements();
        return new PageImpl<>(content, pageable, total);
    }

    @Override
    public List<ENTITY> findAllForModel(final Object queryModel) {
        Specification<ENTITY> specification = Query.querySpecificationForModel(queryModel);
        return findAll(specification);
    }

    @Override
    public Page<ENTITY> findAllForModel(Object queryModel, Pageable pageable) {
        Specification<ENTITY> specification = Query.querySpecificationForModel(queryModel);
        return findAll(specification, pageable);
    }

    @Override
    public <R> List<R> findAllForModel(Object queryModel, Class<R> responseType) {
        return findAllForModel(queryModel, responseType, null).getContent();
    }

    @Override
    public <R> Page<R> findAllForModel(Object queryModel, Class<R> responseType, Pageable pageable) {
        List<String> objProperties = new LinkedList<>();
        Page<Tuple> tuplePage = queryForModelAndResultType(queryModel, responseType, pageable, objProperties);
        return covertTuplePage2ObjPage(tuplePage, objProperties, responseType, pageable);
    }

    @Override
    public <R> List<R> findAllForModel(Specification<ENTITY> specification, Class<R> responseType) {
        return findAllForModel(specification, responseType, null).getContent();
    }

    @Override
    public <R> Page<R> findAllForModel(Specification<ENTITY> specification, Class<R> responseType, Pageable pageable) {
        List<String> objProperties = new LinkedList<>();
        Page<Tuple> tuplePage = queryForModelAndResultType(specification, responseType, pageable, objProperties);
        return covertTuplePage2ObjPage(tuplePage, objProperties, responseType, pageable);
    }

    @Override
    public Page<Tuple> findAllForTuple(Specification<ENTITY> specification, Pageable pageable) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Tuple> query = builder.createTupleQuery();
        Root<ENTITY> root = query.from(getDomainClass());

        Predicate predicate = specification.toPredicate(root, query, builder);

        query.where(predicate);

        TypedQuery<Tuple> tupleTypedQuery = em.createQuery(query);

        if (pageable == null)
            return new PageImpl<>(tupleTypedQuery.getResultList());

        tupleTypedQuery.setFirstResult(pageable.getOffset());
        tupleTypedQuery.setMaxResults(pageable.getPageSize());

        Long total = executeCountQuery2(getCountQuery(specification));

        List<Tuple> content = total > pageable.getOffset() ? tupleTypedQuery.getResultList() : Collections.<Tuple>emptyList();

        return new PageImpl<>(content, pageable, total);
    }

    @Override
    public List<Tuple> findAllForTuple(Specification<ENTITY> specification) {
        return findAllForTuple(specification,null).getContent();
    }


}
